import multiprocessing
import random

from OpenGL.GL import *  # 导入OpenGL库，用于进行3D渲染
from OpenGL.GLUT import *  # 导入GLUT库，用于创建OpenGL上下文（这里没有实际使用）

import AudioManager
import Constants
import math
import Geometry
import numpy as np
import pyrr, glfw
import Materials
import Utils
from Utils import logger, SaveManager, take_screen_shot
from MeshData import SkyboxMesh, CloudsMesh, SelectFrameMesh, UIMesh, TextMesh, DestroyedBlockMesh, SkyboxMainMenuMesh, \
    UIBlockMesh
from WorldData import World
from World import BlockType, BlockRegistry, ItemRegistry
import AABB
import Inventory
import UI_Elements

import time
from AudioManager import audio_player

global \
    window, \
    camera, \
    world, \
    last_load_center, \
    game_clock, \
    ui_manager, \
    particle_manager, \
    cursor_lock_state, \
    tk_quit, \
    in_game


class GameClock:
    def __init__(self):
        self._start_time = time.time()
        self._last_time = self._start_time
        self._time_scale = 1.0
        self._delta_time = 0

    def update(self):
        current_time = time.time()
        self._delta_time = (current_time - self._last_time) * self._time_scale
        self._last_time = current_time

    @property
    def delta_time(self):
        return self._delta_time

    def set_time_scale(self, value):
        self._time_scale = value


class Transform:
    def __init__(self, position=np.array([0.0, 0.0, 0.0], dtype=np.float32), yaw=0.0, pitch=0.0):
        self.position, self.yaw, self.pitch = position, yaw, pitch


class Camera:
    def __init__(self,
                 opaque_material=None,
                 skybox_material=None,
                 skybox_main_menu_material=None,
                 clouds_material=None,
                 select_frame_material=None,
                 half_transparent_material=None,
                 transform=Transform(position=np.array([0.0, 30.0, 0.0], dtype=np.float32), yaw=0.0, pitch=0.0),
                 up=np.array([0.0, 1.0, 0.0], dtype=np.float32),
                 fovy=90,
                 aspect=Constants.DISPLAY[0] / Constants.DISPLAY[1],
                 near=0.01,
                 far=100.0,
                 save_manager=None
                 ):
        self.opaque_material = opaque_material
        self.skybox_material = skybox_material
        self.skybox_main_menu_material = skybox_main_menu_material
        self.clouds_material = clouds_material
        self.select_frame_material = select_frame_material
        self.save_manager = save_manager

        self.transform = transform

        self.up = up
        self.fovy = fovy
        self.aspect = aspect
        self.near = near
        self.far = far

        self.forward = np.array([0.0, 0.0, 1.0], dtype=np.float32)
        self.forward_dim2 = np.array([self.forward[0], 0, self.forward[2]], dtype=np.float32)
        self.right = np.cross(self.forward, self.up)
        self.right_dim2 = np.array([self.right[0], 0, self.right[2]], dtype=np.float32)

        self.aabb = AABB.Collider(np.array([-0.3, -1.5, -0.3], dtype=np.float32), np.array([0.3, 0.1, 0.3], dtype=np.float32))

        self.skybox = SkyboxMesh(skybox_material, self)
        self.skybox_main_menu = SkyboxMainMenuMesh(skybox_main_menu_material, self, game_clock)
        self.clouds = CloudsMesh(game_clock, clouds_material, self, world)
        self.select_frame = SelectFrameMesh(select_frame_material)

        self.rotation_matrix, self.translation_matrix, self.view_matrix, self.projection_matrix = None, None, None, None

        self.on_ground = False
        self.hit_info = None
        self.first_mouse = True
        self.last_x, self.last_y = Constants.DISPLAY[0] / 2, Constants.DISPLAY[1] / 2
        self.apply()

    def init_transform(self):
        if self.save_manager is not None:
            transform_data = self.save_manager.load_transform_data()
            if transform_data is not None:
                self.transform = transform_data

    def reset_transform(self):
        self.transform = Transform(position=np.array([0.0, 95.0, 0.0], dtype=np.float32), yaw=0.0, pitch=0.0)

    def save(self):
        self.save_manager.save_transform_data(self.transform)

    def ray_voxel_intersection(self, start, direction, max_distance=6):
        """
                带碰撞检测的射线投射

                返回:
                    hit: 是否命中
                    voxel: 命中的体素坐标
                    distance: 命中距离
                    normal: 命中面法线
                """
        direction = np.array(direction, dtype=np.float32)
        direction /= np.linalg.norm(direction)

        try:
            current_voxel = np.floor(start).astype(int)
            step = np.sign(direction).astype(int)
            next_boundary = current_voxel + (step > 0).astype(int)
            t_max = (next_boundary - start) / direction
            t_delta = step / direction

            last_voxel = None
            t = 0.0

            while t < max_distance:
                if world.get_block(current_voxel):
                    # 计算命中面法线
                    normal = np.zeros(3)
                    if last_voxel is not None:
                        normal = current_voxel - last_voxel
                    return True, current_voxel.copy(), t, normal

                last_voxel = current_voxel.copy()

                # 步进到下一个体素
                if t_max[0] < t_max[1] and t_max[0] < t_max[2]:
                    current_voxel[0] += step[0]
                    t = t_max[0]
                    t_max[0] += t_delta[0]
                elif t_max[1] < t_max[2]:
                    current_voxel[1] += step[1]
                    t = t_max[1]
                    t_max[1] += t_delta[1]
                else:
                    current_voxel[2] += step[2]
                    t = t_max[2]
                    t_max[2] += t_delta[2]

            return False, None, max_distance, None
        except ZeroDivisionError as e:
            logger.error(e, 'ZeroDivisionError')
            return False, None, max_distance, None

    @staticmethod
    def normalize(vector):
        length = math.sqrt(sum([e ** 2 for e in vector]))
        if length > 0:
            return np.array([e / length for e in vector], dtype=np.float32)
        else:
            return np.array([0, 0, 0], dtype=np.float32)

    def translate(self, offset):
        block_poses = world.get_nearby_block_poses(self.transform.position, 2)
        #block_poses = []
        block_colliders = []
        for block_pos in block_poses:
            block_colliders.append(AABB.Collider(np.array([block_pos[0] - 0.5, block_pos[1] - 0.5, block_pos[2] - 0.5], dtype=np.float32),
                                                 np.array(
                                                     [block_pos[0] + 0.5, block_pos[1] + 0.5, block_pos[2] + 0.5], dtype=np.float32)))
        if Constants.ThroughWalls is False:
            offset, self.on_ground = self.aabb.block_collision_processor(block_colliders, offset)
        #self.on_ground = offset[0] == 0
        #print(self.on_ground)
        self.transform.position += offset
        self.aabb.min_pos = self.transform.position + np.array([-0.3, -1.5, -0.3], dtype=np.float32)
        self.aabb.max_pos = self.transform.position + np.array([0.3, 0.1, 0.3], dtype=np.float32)
        

    def rotate(self, yaw_offset, pitch_offset):
        # print(yaw_offset, pitch_offset)
        self.transform.yaw += yaw_offset
        self.transform.pitch += pitch_offset
        if self.transform.pitch >= math.pi / 2:
            self.transform.pitch = math.pi / 2
        elif self.transform.pitch <= -math.pi / 2:
            self.transform.pitch = -math.pi / 2
        self.forward = self.normalize(np.array([
            -math.cos(self.transform.pitch) * math.sin(self.transform.yaw),  # X: 水平转向（yaw） + 俯仰（pitch）影响
            math.sin(self.transform.pitch),  # Y: 抬头（pitch 为正时，Y 为负）
            -math.cos(self.transform.pitch) * math.cos(self.transform.yaw)  # Z: 水平前进（yaw） + 俯仰（pitch）影响
        ], dtype=np.float32))
        self.forward_dim2 = np.array([
            math.sin(self.transform.yaw),
            0.0,
            math.cos(self.transform.yaw)
        ], dtype=np.float32)
        self.right = np.cross(self.forward, self.up)
        self.right_dim2 = np.array([
            math.sin(self.transform.yaw - math.pi / 2),
            0.0,
            math.cos(self.transform.yaw - math.pi / 2)
        ], dtype=np.float32)

    def set_fovy(self, value):
        self.fovy = value

    def set_aspect(self, value):
        self.aspect = value

    def set_near(self, value):
        self.near = value

    def set_far(self, value):
        self.far = value

    def apply(self):
        self.rotation_matrix = pyrr.matrix44.multiply(
            pyrr.Matrix44.from_y_rotation(self.transform.yaw),
            pyrr.Matrix44.from_x_rotation(self.transform.pitch)
        )
        self.translation_matrix = pyrr.matrix44.create_from_translation(-self.transform.position)
        self.view_matrix = pyrr.matrix44.multiply(self.translation_matrix, self.rotation_matrix)
        self.projection_matrix = pyrr.matrix44.create_perspective_projection_matrix(
            fovy=self.fovy, aspect=self.aspect, near=self.near, far=self.far
        )

        self.draw_environment()
        self.draw_skybox()
        self.draw_clouds()

        # self.select_block()

    def draw_environment(self):
        if self.opaque_material is not None:
            # 处理场景物件
            glUseProgram(self.opaque_material.shader)
            glUniformMatrix4fv(self.opaque_material.view_loc, 1, GL_FALSE, self.view_matrix)
            glUniformMatrix4fv(self.opaque_material.projection_loc, 1, GL_FALSE, self.projection_matrix)
            glUniform3f(self.opaque_material.camera_pos_loc, -self.transform.position[0], -self.transform.position[1],
                        -self.transform.position[2])

    def draw_skybox(self):
        if self.skybox_material is not None:
            # 处理天空盒
            glUseProgram(self.skybox_material.shader)
            glUniformMatrix4fv(self.skybox_material.view_loc, 1, GL_FALSE, self.view_matrix)
            glUniformMatrix4fv(self.skybox_material.projection_loc, 1, GL_FALSE, self.projection_matrix)
            glDepthMask(GL_FALSE)
            self.skybox.draw()
            glDepthMask(GL_TRUE)

    def draw_main_menu_skybox(self):
        if self.skybox_main_menu_material is not None:
            # 处理天空盒
            glUseProgram(self.skybox_main_menu_material.shader)
            glUniformMatrix4fv(self.skybox_main_menu_material.view_loc, 1, GL_FALSE, self.view_matrix)
            glUniformMatrix4fv(self.skybox_main_menu_material.projection_loc, 1, GL_FALSE, self.projection_matrix)
            # glDepthMask(GL_FALSE)
            self.skybox_main_menu.draw()
            # glDepthMask(GL_TRUE)

    def draw_clouds(self):
        if self.clouds_material is not None:
            # 处理云层
            glUseProgram(self.clouds_material.shader)
            glUniformMatrix4fv(self.clouds_material.view_loc, 1, GL_FALSE, self.view_matrix)
            glUniformMatrix4fv(self.clouds_material.projection_loc, 1, GL_FALSE, self.projection_matrix)
            self.clouds.draw()

    def select_block(self):
        self.hit_info = self.ray_voxel_intersection(
            self.transform.position + np.array([0.5, 0.5, 0.5], dtype=np.float32),
            self.forward,
            Constants.MAX_BLOCK_DISTANCE
        )
        if self.hit_info[0]:
            hit_pos = self.hit_info[1]
            if self.select_frame_material is not None:
                # 处理选中提示
                glUseProgram(self.select_frame_material.shader)
                glUniformMatrix4fv(self.select_frame_material.view_loc, 1, GL_FALSE, self.view_matrix)
                glUniformMatrix4fv(self.select_frame_material.projection_loc, 1, GL_FALSE, self.projection_matrix)
                self.select_frame.draw(hit_pos)

    def mouse_button_down_handler(self, button, action):
        if in_game and action == glfw.PRESS:
            self.set_or_destroy_block(button)

    def mouse_move_handler(self, pos):
        if in_game:
            if cursor_lock_state:
                if self.first_mouse:
                    self.last_x, self.last_y = pos[0], pos[1]
                    self.first_mouse = False
                x_offset, y_offset = self.last_x - pos[0], self.last_y - pos[1]
                self.last_x, self.last_y = pos[0], pos[1]
                self.rotate(x_offset * Constants.SENSITIVITY,
                            y_offset * Constants.SENSITIVITY)
            else:
                self.first_mouse = True

    def set_or_destroy_block(self, button) -> bool:
        """放置或破坏方块"""
        if cursor_lock_state and self.hit_info is not None and self.hit_info[0]:
            if button == glfw.MOUSE_BUTTON_LEFT:
                return self._set_block()  # 破坏方块
            elif button == glfw.MOUSE_BUTTON_RIGHT:
                return self._destroy_block()  # 放置方块

        return True

    def _destroy_block(self) -> bool:
        """破坏方块"""
        item_id = ui_manager.player_inventory_data.inventory[ui_manager.selected_slot_index].item_id
        if item_id == -1:
            return False
        world.player_set_block(self.hit_info[1] - self.hit_info[3], item_id, False)
        ui_manager.player_inventory_data.remove_item(item_id)
        ui_manager.update_inventory_ui()
        audio_player.play_sound(
            sound_id=BlockRegistry.get(item_id).place_sound,
            position=(self.hit_info[1] - self.hit_info[3] - self.transform.position)
        )
        return True

    def _set_block(self) -> bool:
        """设置方块"""
        block_type = world.get_block(self.hit_info[1])
        if block_type == -1:
            return False
        ui_manager.player_inventory_data.add_item(block_type)
        ui_manager.update_inventory_ui()
        particle_manager.destroy_block_particle(self.hit_info[1], block_type)
        world.player_set_block(self.hit_info[1], BlockType.Air)
        audio_player.play_sound(
            sound_id=BlockRegistry.get(block_type).dig_sound,
            position=(self.hit_info[1] - self.transform.position)
        )
        return True

def test_func():
    print("button clicked!")

class UIManager:
    def __init__(self, save_manager, temp_inventory_data=None):

        self.current_aspect = Constants.DISPLAY[0] / Constants.DISPLAY[1]
        self.current_display = (Constants.DISPLAY[0], Constants.DISPLAY[1])
        self.save_manager = save_manager
        self.player_inventory_data = Inventory.PlayerInventory(save_manager=None)
        self.is_loading = False

        self.crosshair_mesh = UIMesh(
            image_path='Resources/UI/crosshair.png',
            screen_position=(0.0, 0.0),
            pixel_size=(0.025, 0.04444),
            ui_manager=self
        )
        self.inventory_mesh = UIMesh(
            image_path='Resources/UI/dark_background.png',
            screen_position=(0.0, 0.0),
            pixel_size=(1.0, 1.0),
            enabled=False,
            ui_manager=self,
            child_mesh=[
                UIMesh(
                    image_path='Resources/UI/inventory.png',
                    screen_position=(0.0, 0.0),
                    pixel_size=(0.36667, 0.77037),
                    enabled=True,
                    child_mesh=[
                        TextMesh(
                            text='Inventory',
                            screen_position=(-0.09896, 0.34722),
                            font_scale=0.00048,
                            enabled=True,
                            ui_manager=self
                        ),
                    ]
                )
            ]
        )
        self.inventory_select_group = UI_Elements.ElementGroup(
            buttons=[],
            static_meshes=[],
            enabled=False
        )
        for i in range(0, len(BlockRegistry)):
            ix, iy = i % 8, i // 8
            self.inventory_mesh.child_mesh.append(
                UIBlockMesh(
                    block_type=i,
                    scale=0.02,
                    screen_position=(0.0375 * ix - 0.15, -0.06675 * iy + 0.29),
                    enabled=True,
                    ui_manager=self
                )
            )
            self.inventory_select_group.buttons.append(
                UI_Elements.Button(
                    screen_position=(0.0375 * ix - 0.15, -0.06675 * iy + 0.29),
                    pixel_size=(0.13 * 0.25, 0.2335 * 0.25),
                    ui_manager=self,
                    text="",
                    font_size=0.00036,
                    background_image_path=None,
                    hovered_background_image_path="Resources/UI/slot_select.png",
                    callback=lambda current_i=i: self.set_hotbar_item(current_i, 64)
                )
            )

        self.hot_bar_mesh = UIMesh(
            image_path='Resources/UI/hotbar.png',
            screen_position=(0.0, -0.45926),
            pixel_size=(0.37917, 0.08148),
            ui_manager=self,
            child_mesh=[
                           UIMesh(
                               image_path='Resources/UI/hotbar_select.png',
                               screen_position=((-0.37917 * 0) / 9, 0.0),
                               pixel_size=(0.04688, 0.08333),
                               enabled=True,
                               ui_manager=self
                           ),
                           TextMesh(
                               text='Item Name',
                               screen_position=(0, 0.09259),
                               font_scale=0.00048,
                               padding_mode=1,
                               enabled=True,
                               ui_manager=self,
                           ),
                       ] + [UIBlockMesh(
                block_type=0,
                scale=0.02,
                screen_position=((-0.37917) / 9, 0.0),
                enabled=True,
                ui_manager=self,
                game_clock=game_clock,
                child_mesh=[
                    TextMesh(
                        text='00',
                        screen_position=(0.01302, -0.47685),
                        font_scale=0.00026,
                        enabled=True,
                        ui_manager=self,
                    )
                ]
            ) for _ in range(0, self.player_inventory_data.inventory_size)],
        )
        self.hand_mesh = UIBlockMesh(
            block_type=0,
            scale=0.15,
            z_pos=0.5,
            screen_position=(0.405, -0.415),
            rotation=(-30, -70, 0),
            enabled=True,
            ui_manager=self,
            game_clock=game_clock
        )

        self.info_text_mesh = TextMesh(
            text='',
            screen_position=(0, 0.46296),
            font_scale=0.00052,
            padding_mode=1,
            enabled=True,
            ui_manager=self
        )

        self.meshes = [  # 越上越靠前
            self.hot_bar_mesh,
            self.inventory_mesh,
            self.hand_mesh,
            self.info_text_mesh,
            self.crosshair_mesh,
        ]

        self.escape_element_group = UI_Elements.ElementGroup(
            buttons=[
                UI_Elements.Button(
                    screen_position=(0.0, 0.1),
                    pixel_size=(0.36667, 0.08148),
                    ui_manager=self,
                    text="Back To Game",
                    callback=self.switch_escape_menu_ui
                ),
                UI_Elements.Button(
                    screen_position=(0.0, -0.1),
                    pixel_size=(0.36667, 0.08148),
                    ui_manager=self,
                    text="Quit Game",
                    callback=game_quit
                )
            ],
            static_meshes=[
                UIMesh(
                    image_path='Resources/UI/dark_background.png',
                    screen_position=(0.0, 0.0),
                    pixel_size=(1.0, 1.0),
                    enabled=True,
                    ui_manager=self
                )
            ],
            enabled=False
        )

        self.main_menu_elements_group = UI_Elements.ElementGroup(
            buttons=[
                UI_Elements.Button(
                    screen_position=(0.0, -0.0375),
                    pixel_size=(0.36667, 0.07148),
                    ui_manager=self,
                    text="Create New World",
                    font_size=0.00036,
                    callback=self.begin_loading_new_world
                ),
                UI_Elements.Button(
                    screen_position=(0.0, -0.13095),
                    pixel_size=(0.36667, 0.07148),
                    ui_manager=self,
                    text="Play Old World",
                    font_size=0.00036,
                    callback=self.begin_loading
                ),
                UI_Elements.Button(
                    screen_position=(0.0, -0.2247),
                    pixel_size=(0.36667, 0.07148),
                    ui_manager=self,
                    text="  Quit Game",
                    font_size=0.00036,
                    callback=game_quit
                )
            ],
            static_meshes=[
                UIMesh(
                    image_path='Resources/UI/title2.png',
                    screen_position=(0.0, 0.25),
                    pixel_size=(0.5335, 0.20879),
                    ui_manager=self,
                    child_mesh=[
                        TextMesh(
                            text=Constants.RANDOM_TIPS[random.randint(0, len(Constants.RANDOM_TIPS) - 1)],
                            screen_position=(0.25, -0.05),
                            rotation=-20.0,
                            tint=(1.0, 1.0, 0.0, 1.0),
                            font_scale=0.0005,
                            padding_mode=1,
                            enabled=True,
                            ui_manager=self
                        )
                    ]
                ),
                TextMesh(
                    text='Pycraft Alpha 0.0.1',
                    screen_position=(-0.38, -0.475),
                    font_scale=0.00042,
                    padding_mode=1,
                    enabled=True,
                    ui_manager=self
                ),
                TextMesh(
                    text='Copyright NULL. Do not distribute!',
                    screen_position=(0.295, -0.475),
                    font_scale=0.00042,
                    padding_mode=1,
                    enabled=True,
                    ui_manager=self
                ),
            ]
        )

        self.main_menu_loading_elements_group = UI_Elements.ElementGroup(
            buttons=[],
            static_meshes=[
                TextMesh(
                    text='Compiling Numba',
                    screen_position=(0.0, 0.0),
                    font_scale=0.0004,
                    padding_mode=1,
                    enabled=True,
                    ui_manager=self
                ),
                UI_Elements.ProgressBar(
                    screen_position=(0.0, -0.05),
                    pixel_size=(0.225, 0.01),
                    ui_manager=self
                ),
                UIMesh(
                    image_path='Resources/UI/dirt_repeat.jpg',
                    screen_position=(0.0, 0.0),
                    pixel_size=(1.0, 1.0),
                    ui_manager=self,
                    enabled=False
                ),
                UIBlockMesh(
                    block_type=12,
                    scale=0.05,
                    screen_position=(-0.45, -0.425),
                    enabled=True,
                    ui_manager=self,
                    rot_speed=60,
                    game_clock=game_clock,
                    child_mesh=[
                        TextMesh(
                            text='Tips: Click Mouse Button Left To Break Blocks!',
                            screen_position=(0.315, -0.0275),
                            font_scale=0.000425,
                            tint=(1.0, 0.0, 1.0, 1.0),
                            padding_mode=1,
                            enabled=True,
                            ui_manager=self
                        ),
                    ]
                )
            ],
            enabled=False
        )

        self.logo = UIMesh(
            image_path='Resources/UI/start_logo.png',
            screen_position=(0.0, 0.0),
            pixel_size=(1.0, 1.0),
            ui_manager=self
        )
        self.logo_duration = 2.0
        self.logo_time = self.logo_duration

        self.selected_slot_index = 0
        self.switch_hot_bar_select_slot(self.selected_slot_index)

    def resized_window(self, new_width, new_height):
        self.current_aspect, self.current_display = new_width / new_height, (new_width, new_height)

    def ui_key_down_handler(self, key, action):
        if in_game:
            if action == glfw.PRESS:
                if glfw.KEY_1 <= key <= glfw.KEY_9:
                    self.selected_slot_index = key - glfw.KEY_1
                    self.switch_hot_bar_select_slot(self.selected_slot_index)
                elif key == glfw.KEY_ESCAPE:
                    self.switch_escape_menu_ui()
                elif key == glfw.KEY_E:
                    self.switch_inventory_ui()

    def ui_mouse_button_down_h(self, button_type, action):
        if in_game:
            self.ui_mouse_button_down_handler(button_type, action)
        else:
            self.ui_mouse_button_down_handler_menu(button_type, action)

    def ui_mouse_button_down_handler(self, button_type, action):
        if self.escape_element_group.enabled:
            self.escape_element_group.mouse_button_down_handler(button_type, action)
        if self.inventory_select_group.enabled:
            self.inventory_select_group.mouse_button_down_handler(button_type, action)

    def ui_mouse_button_down_handler_menu(self, button_type, action):
        self.main_menu_elements_group.mouse_button_down_handler(button_type, action)

    def ui_mouse_scroll_h(self, y_offset):
        if in_game:
            self.ui_mouse_scroll_handler(y_offset)

    def ui_mouse_scroll_handler(self, y_offset):
        self.selected_slot_index -= int(y_offset)
        self.selected_slot_index %= 9
        self.switch_hot_bar_select_slot(self.selected_slot_index)

    def ui_mouse_move_h(self, pos):
        if in_game:
            self.ui_mouse_move_handler(pos)
        else:
            self.ui_mouse_move_handler_menu(pos)

    def ui_mouse_move_handler(self, pos):
        self.escape_element_group.mouse_move_handler(pos)
        self.inventory_select_group.mouse_move_handler(pos)

    def ui_mouse_move_handler_menu(self, pos):
        self.main_menu_elements_group.mouse_move_handler(pos)
        # print(pos)

    def switch_inventory_ui(self):
        global cursor_lock_state
        self.inventory_mesh.enabled = not self.inventory_mesh.enabled
        self.inventory_select_group.enabled = self.inventory_mesh.enabled
        cursor_lock_state = not self.inventory_mesh.enabled
        glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_DISABLED if cursor_lock_state else glfw.CURSOR_NORMAL)

    def switch_escape_menu_ui(self):
        if self.inventory_mesh.enabled:
            self.switch_inventory_ui()
            return
        global cursor_lock_state
        self.escape_element_group.enabled = not self.escape_element_group.enabled
        self.hot_bar_mesh.enabled = not self.escape_element_group.enabled
        cursor_lock_state = not self.escape_element_group.enabled
        glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_DISABLED if cursor_lock_state else glfw.CURSOR_NORMAL)

    def set_hotbar_item(self, item_id, item_amount):
        print(111)
        self.player_inventory_data.set_item(item_id, item_amount, self.selected_slot_index)
        self.update_inventory_ui()

    def switch_hot_bar_select_slot(self, index):
        """玩家切换选择插槽"""
        self.hot_bar_mesh.child_mesh[0].screen_position = ((-0.37917 * (4 - index)) / 9, 0.0)
        selected_id = self.player_inventory_data.inventory[index].item_id
        if selected_id != -1:
            self.hot_bar_mesh.child_mesh[1].set_text(ItemRegistry[selected_id].name)
            self.hand_mesh.set_block_texture(selected_id)
            self.hot_bar_mesh.child_mesh[1].enabled = True
            self.hand_mesh.enabled = True
        else:
            self.hot_bar_mesh.child_mesh[1].enabled = False
            self.hand_mesh.enabled = False

    def update_inventory_ui(self):
        for i in range(0, self.player_inventory_data.inventory_size):
            item_id = self.player_inventory_data.inventory[i].item_id
            inventory_slot_index = i + 2
            if item_id == -1:
                self.hot_bar_mesh.child_mesh[inventory_slot_index].enabled = False
            else:
                self.hot_bar_mesh.child_mesh[inventory_slot_index].screen_position = (
                    (-0.37917 * 0.99075 * (4 - i)) / 9, 0.0)
                self.hot_bar_mesh.child_mesh[inventory_slot_index].set_block_texture(item_id)
                self.hot_bar_mesh.child_mesh[inventory_slot_index].child_mesh[0].set_text(
                    str(self.player_inventory_data.inventory[i].item_amount))
                self.hot_bar_mesh.child_mesh[inventory_slot_index].enabled = True
                self.hand_mesh.swing()
        self.switch_hot_bar_select_slot(self.selected_slot_index)

    def draw_menu(self):

        camera.draw_main_menu_skybox()

        self.main_menu_loading_elements_group.draw()
        self.main_menu_elements_group.draw()
        if self.logo_time > 0.9 * self.logo_duration:
            self.logo_time -= game_clock.delta_time
            glDisable(GL_DEPTH_TEST)
            self.logo.draw()
            glEnable(GL_DEPTH_TEST)
        elif self.logo_time > 0:
            self.logo.set_tint((1.0, 1.0, 1.0, self.logo_time / (0.9 * self.logo_duration)))
            glDisable(GL_DEPTH_TEST)
            self.logo.draw()
            glEnable(GL_DEPTH_TEST)
            self.logo_time -= game_clock.delta_time
        elif self.logo.enabled:
            self.logo.enabled = False
        self.main_menu_elements_group.static_meshes[0].child_mesh[0].font_scale = 0.0005 + (
                math.sin(4 * time.time()) + 1) * 0.5 * 0.000075

    def draw(self):
        self.escape_element_group.draw()
        self.inventory_select_group.draw()
        for i in range(0, len(self.meshes)):
            self.meshes[i].draw()


    def begin_loading(self):
        self.save_manager.create_directory()
        self.player_inventory_data = Inventory.PlayerInventory(save_manager=self.save_manager)
        self.main_menu_loading_elements_group.enabled = True
        self.main_menu_elements_group.enabled = False
        self.is_loading = True
        self.update_inventory_ui()
        begin_loading_world()

    def begin_loading_new_world(self):
        self.save_manager.delete_save()
        self.begin_loading()

    def loading_world(self):
        progress = world.draw_count
        target = (Constants.WORLD_SIZE - 2) ** 2
        self.main_menu_loading_elements_group.static_meshes[1].set_progress(progress / target)
        if progress == 0:
            self.main_menu_loading_elements_group.static_meshes[0].set_text('Compiling Numba')
        elif progress < target:
            self.main_menu_loading_elements_group.static_meshes[0].set_text(
                f'Building Terrain ({int(progress * 100 / target)}%)')
        else:
            self.main_menu_loading_elements_group.static_meshes[0].set_text('Done!')
            start_game()


class ParticleManager:
    def __init__(self, opaque_material=None):
        self.particles = []
        self.opaque_material = opaque_material

    def destroy_block_particle(self, block_position, block_type):
        self.particles.append(
            DestroyedBlockMesh(self.opaque_material, block_position, block_type, game_clock, self, world))

    def draw(self):
        for particle in self.particles:
            particle.draw(camera.transform.position)


# 每帧更新
def update():
    global window, camera, world, last_load_center, game_clock, ui_manager, particle_manager, tk_quit, in_game

    frame_count = 0
    while not glfw.window_should_close(window):  # 主循环

        game_clock.update()

        if Constants.DEBUG_MODE:
            if tk_quit:
                tk_quit = False
                game_quit()
        else:
            fast_quit()

        # 处理输入事件（包括按键）
        glfw.poll_events()
        glfw.swap_buffers(window)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        if in_game:

            # 渲染
            if cursor_lock_state:
                move()
            camera.apply()

            # 加载更多区块
            load_more_chunk()

            world.draw_world()
            particle_manager.draw()

            camera.select_block()
            frame_count += 1
            frame_count %= 100
            if frame_count == 0:
                try:
                    ui_manager.info_text_mesh.set_text(
                        f'X: {int(camera.transform.position[0])}, Y: {int(camera.transform.position[1])}, Z: {int(camera.transform.position[2])}, FPS: {int(1 / game_clock.delta_time)}, VSYNC: {"ON" if Constants.VSYNC else "OFF"}')
                except ZeroDivisionError:
                    pass
            ui_manager.draw()
        else:
            ui_manager.draw_menu()
            if ui_manager.is_loading:
                ui_manager.loading_world()
                world.draw_world()

    logger.info('程序退出！')
    # 退出 GLFW
    glfw.terminate()


def normalize(vector):
    length = math.sqrt(sum([e ** 2 for e in vector]))
    if length > 0:
        return np.array([e / length for e in vector], dtype=np.float32)
    else:
        return np.array([0, 0, 0], dtype=np.float32)


def load_more_chunk():
    global last_load_center
    world.save_world()
    cur_load_center = Utils.get_chunk_pos_by_world_pos(camera.transform.position)
    distance = math.fabs(cur_load_center[0] - last_load_center[0]) // Constants.CHUNK_SIZE + math.fabs(
        cur_load_center[1] - last_load_center[1]) // Constants.CHUNK_SIZE
    if Constants.INFINITY_WORLD and distance >= 2:
        world.generate_more_chunk(camera.transform.position)
        last_load_center = Utils.get_chunk_pos_by_world_pos(camera.transform.position)


velocity_y = 0
# 移动方法
def move():
    global game_clock, velocity_y
    velocity = np.array([0.0, 0.0, 0.0], dtype=np.float32)
    if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS:
        velocity += camera.forward_dim2 * -0.1
    if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS:
        velocity += camera.forward_dim2 * 0.1
    if glfw.get_key(window, glfw.KEY_A) == glfw.PRESS:
        velocity += camera.right_dim2 * 0.1
    if glfw.get_key(window, glfw.KEY_D) == glfw.PRESS:
        velocity += camera.right_dim2 * -0.1
    if Constants.FLY_MODE:
        if glfw.get_key(window, glfw.KEY_SPACE) == glfw.PRESS:
            velocity += camera.up * 0.1
        if glfw.get_key(window, glfw.KEY_Z) == glfw.PRESS:
            velocity += camera.up * -0.1
        camera.translate(normalize(velocity) * Constants.MOVE_SPEED * game_clock.delta_time)
    else:
        if velocity_y > -Constants.GRAVITY_VALUE * 0.25:
            velocity_y -= Constants.GRAVITY_VALUE * game_clock.delta_time
        if glfw.get_key(window, glfw.KEY_SPACE) == glfw.PRESS and camera.on_ground:
            velocity_y = 10

        camera.translate(normalize(velocity) * Constants.MOVE_SPEED * game_clock.delta_time + np.array(
            [0, velocity_y * game_clock.delta_time, 0], dtype=np.float32))

def game_save():
    try:
        camera.save()
        world.save_world()
        ui_manager.player_inventory_data.save()
    except AttributeError:
        pass


def game_quit():
    global window
    game_save()
    glfw.set_window_should_close(window, True)


# 快捷退出
def fast_quit():
    pass
    # keys = pygame.key.get_pressed()
    # if keys[K_q]:
    #     game_quit()


def on_key(window, key, scancode, action, mods):
    global ui_manager, in_game
    ui_manager.ui_key_down_handler(key, action)
    if key == glfw.KEY_Q and action == glfw.PRESS:
        game_quit()
    elif key == glfw.KEY_P and action == glfw.PRESS:
        take_screen_shot(window)
    # if in_game:
    #     if key == glfw.KEY_SPACE and action == glfw.PRESS:
    #         jump()
    #         print("jump!")


# 全局变量存储上一次鼠标位置（仅用于初始化）
first_mouse = True
last_x, last_y = Constants.DISPLAY[0] / 2, Constants.DISPLAY[1] / 2  # 假设窗口中心为初始坐标


# 鼠标移动回调函数
def on_mouse_move(window, xpos, ypos):
    global ui_manager, camera
    ui_manager.ui_mouse_move_h((xpos, ypos))
    camera.mouse_move_handler((xpos, ypos))


# 鼠标按键回调函数
def on_mouse_click(window, button, action, mods):
    global ui_manager, camera
    ui_manager.ui_mouse_button_down_h(button, action)
    camera.mouse_button_down_handler(button, action)


# 鼠标滚轮回调函数
def on_mouse_scroll(window, x_offset, y_offset):
    global ui_manager
    ui_manager.ui_mouse_scroll_h(y_offset)


def on_window_resize(window, width, height):
    global camera
    if height == 0:  # 防止除以零
        height = 1
    camera.aspect = width / height
    ui_manager.resized_window(width, height)
    glViewport(0, 0, width, height)  # 设置视口大小
    glMatrixMode(GL_MODELVIEW)  # 切换回模型视图矩阵
    glLoadIdentity()  # 重置模型视图矩阵


# 设置窗口图标
def set_icon_win32(window, icon_path):
    try:
        import ctypes
        from ctypes import wintypes

        # 加载 .ico 文件
        icon_big = 1
        wm_section = 0x80
        hwnd = glfw.get_win32_window(window)

        # 调用 API
        user32 = ctypes.windll.user32
        hicon = user32.LoadImageW(0, icon_path, 1, 0, 0, 0x00000010)
        if hicon:
            user32.SendMessageW(hwnd, wm_section, icon_big, hicon)
    except Exception as e:
        logger.error(f"Error: {e}")


# 准备函数
def prepare():
    if multiprocessing.current_process().name != 'MainProcess':
        return

    AudioManager.audio_init()

    from Utils import JitFunc
    JitFunc.init_jit_func()


# 初始化绘图
def init_graphics():
    global window, camera, world, last_load_center, game_clock, ui_manager, particle_manager, cursor_lock_state, tk_quit, in_game
    if multiprocessing.current_process().name != 'MainProcess':
        return
    # 初始化GLFW
    if not glfw.init():
        raise Exception("Failed to initialize GLFW")
    # 获取主显示器的视频模式（包含分辨率）
    monitor = glfw.get_primary_monitor()
    mode = glfw.get_video_mode(monitor)
    screen_width, screen_height = mode.size
    logger.info("初始化窗口！")
    if Constants.FULL_SCREEN:
        Constants.DISPLAY = mode.size
    window = glfw.create_window(Constants.DISPLAY[0], Constants.DISPLAY[1], Constants.GAME_TITLE,
                                monitor if Constants.FULL_SCREEN else None, None)
    if not window:
        glfw.terminate()
        raise Exception("Failed to create GLFW window")
    # 获取窗口帧缓冲区大小（考虑边框等）
    framebuffer_size = glfw.get_framebuffer_size(window)
    # 设置窗口位置
    glfw.set_window_pos(window, int((screen_width - framebuffer_size[0]) / 2),
                        int((screen_height - framebuffer_size[1]) / 2))
    set_icon_win32(window, Constants.GAME_ICON)
    # 将当前上下文设置为创建的窗口
    glfw.make_context_current(window)
    # 设置垂直同步
    glfw.swap_interval(1 if Constants.VSYNC else 0)
    # 注册按键回调函数
    glfw.set_key_callback(window, on_key)
    # 注册鼠标回调函数
    glfw.set_cursor_pos_callback(window, on_mouse_move)
    glfw.set_mouse_button_callback(window, on_mouse_click)
    glfw.set_scroll_callback(window, on_mouse_scroll)
    # 注册窗口大小变化回调
    glfw.set_framebuffer_size_callback(window, on_window_resize)

    glEnable(GL_BLEND)  # 启用混合
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glEnable(GL_DEPTH_TEST)  # 启用深度测试
    glEnable(GL_TEXTURE_2D)  # 启用2D纹理
    glDepthFunc(GL_LESS)  # 设置深度测试函数
    glEnable(GL_CULL_FACE)
    glCullFace(GL_BACK)  # 剔除背面
    glFrontFace(GL_CCW)  # 定义正面法线方向为逆时针（默认）
    # glClearColor(33 / 255, 51 / 255, 63 / 255, 255 / 255) # 天蓝色
    glClearColor(0 / 255, 0 / 255, 0 / 255, 255 / 255)  # 黑色

    in_game = False

    opaque_material = Materials.OpaqueMaterial(
        vertex_shader_source=Geometry.vertex_shader,
        fragment_shader_source=Geometry.fragment_shader,
        texture_path='Resources/Blocks/blocks2.png',
        normal_path='Resources/Blocks/blocks_normal.png',
    )
    skybox_material = Materials.SkyboxMaterial(
        vertex_shader_source=Geometry.skybox_vert_shader,
        fragment_shader_source=Geometry.skybox_frag_shader,
        face_textures_path=[
            'Resources/Skybox/skybox1.png', 'Resources/Skybox/skybox2.png', 'Resources/Skybox/skybox3.png',
            'Resources/Skybox/skybox4.png', 'Resources/Skybox/skybox5.png', 'Resources/Skybox/skybox6.png'
        ],
        sun_texture_path='Resources/UI/Test.jpg'
    )
    skybox_main_menu_material = Materials.SkyboxMainMenuMaterial(
        vertex_shader_source=Geometry.skybox_main_menu_vert_shader,
        fragment_shader_source=Geometry.skybox_main_menu_frag_shader,
        face_textures_path=[
            'Resources/Skybox/skybox1.png', 'Resources/Skybox/skybox2.png', 'Resources/Skybox/skybox3.png',
            'Resources/Skybox/skybox4.png', 'Resources/Skybox/skybox5.png', 'Resources/Skybox/skybox6.png'
        ]
    )
    clouds_material = Materials.CloudsMaterial(
        vertex_shader_source=Geometry.clouds_vertex_shader,
        fragment_shader_source=Geometry.clouds_fragment_shader,
        texture_path='Resources/Clouds/clouds.png'
    )
    select_frame_material = Materials.SelectFrameMaterial(
        vertex_shader_source=Geometry.select_frame_vertex_shader,
        fragment_shader_source=Geometry.select_frame_fragment_shader
    )
    shadow_material = Materials.ShadowMaterial(
        vertex_shader_source=Geometry.shadow_vertex_shader,
        fragment_shader_source=Geometry.shadow_fragment_shader
    )

    game_clock = GameClock()
    save_manager = SaveManager(
        world_save_path=Constants.WORLD_SAVE_PATH,
        compressed=False
    )
    world = World(
        game_clock=game_clock,
        save_manager=save_manager,
        opaque_material=opaque_material,
        shadow_material=shadow_material
    )

    camera = Camera(
        opaque_material=opaque_material,
        skybox_material=skybox_material,
        skybox_main_menu_material=skybox_main_menu_material,
        clouds_material=clouds_material,
        select_frame_material=select_frame_material,
        save_manager=save_manager,
        far=750.0
    )  # 创建摄像机

    ui_manager = UIManager(
        save_manager=save_manager
    )

    particle_manager = ParticleManager(
        opaque_material=opaque_material
    )

    tk_quit = False

    if Constants.DEBUG_MODE:
        from DebugWindow import start_debug_window
        start_debug_window(camera=camera, ui_manager=ui_manager, tk_quit=tk_quit, glfw=glfw)


def begin_loading_world():
    """开始加载世界"""
    global world, camera, last_load_center
    camera.init_transform()
    camera.apply()
    last_load_center = Utils.get_chunk_pos_by_world_pos(camera.transform.position)
    world.generate_world(center_pos=last_load_center)
    load_more_chunk()
    camera.reset_transform()
    camera.apply()


def start_game():
    global window, in_game, camera, cursor_lock_state
    cursor_lock_state = True
    # # 启用鼠标捕获
    glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_DISABLED)
    camera.init_transform()
    in_game = True
